/*
 * SharedMem.h
 * 
 * Author: Tarl Hahn
 * Version: 1.0
 *
 * SharedMem is the shared memory area between CPU and the interrupt Controller.
 * This area facilitates communications by holding the io requests from the processes
 * and all the interrupts created by system devices and the timer.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sched.h>
#include <unistd.h>

#include "SharedMem.h"
#include "global.h"

#define PACKSTRING 30

// DataPack prototypes
int DataDestruct(DataPackPtr this);												  // Destruct 
int DestructList(DataPackPtr this);												  // Destruct all nodes in list
char* DataPrint(DataPackPtr this, int* error);									  // tostring

// Shared mem prototypes
int timerSet(SharedMemPtr this, int flag, pthread_mutex_t* lock);                 // set timer flag
int sendRequest(SharedMemPtr this, int pid, int did, pthread_mutex_t* lock);      // add a request to the queue
int sendInterrupt(SharedMemPtr this, int pid, int did, pthread_mutex_t* lock);    // add a interrupt to the queue
int nextRequest(SharedMemPtr this, pthread_mutex_t* lock, int* req_data);         // get a request for the queue
int nextInterrupt(SharedMemPtr this, pthread_mutex_t* lock, int* int_data);       // get an interrupt from the queue
int SharedMemDestruct(SharedMemPtr this); 									      // destruct the shared memory are
char* SharedMemPrintInt(SharedMemPtr this, int* error);							  // interrupt queue tostring
char* SharedMemPrintReq(SharedMemPtr this, int* error);							  // request queue tostring

DataPackPtr DataPackConstruct(int proc_id, int dev_id, DataPackPtr next_pack)
{	
	DataPackPtr pack = (DataPackPtr) malloc(sizeof(DataPack));
	
	// initialze variables
	pack->pid = proc_id; // process ID
	pack->did = dev_id; // device ID
	pack->next = next_pack; // next
	
	// member functions
	pack->destruct = DataDestruct;
	pack->destructAll = DestructList;
	pack->toString = DataPrint;
	
	return pack;
}

// Destruct the DataPack
int DataDestruct(DataPackPtr this)
{
	// if this is null return error
	if (this == (DataPackPtr) NULL) { return NULL_PTR; }
	free(this);
	return NO_ERROR;
}

int DestructList(DataPackPtr this)
{
	// if this is null return error
	if (this == (DataPackPtr) NULL) { return NULL_PTR; }
	// if the next node is not null continue down the list
	if (this->next != (DataPackPtr) NULL) 
	{
		// recursive call to next node
		DestructList(this->next);
	}
	// now that everything else down the list is destructed 
	// free current node.
	free(this);
	return NO_ERROR;
}

// returns a char* representation of the DataPack
char* DataPrint(DataPackPtr this, int* error)
{
	// check for null pointer
	if (this == (DataPackPtr) NULL) 
	{
		*error = NULL_PTR;
		return 0;
	}
	// create a string big enough for the DataPack
	char* string = (char*) malloc(sizeof(char) * PACKSTRING);
	// create char* representation of the dataPack
	snprintf(string, PACKSTRING, "Process ID: %d Device ID: %d", this->pid, this->did);
	return string; // return
}

// Constructor for shared memory area.
SharedMemPtr SharedMemConstruct(void)
{
	SharedMemPtr mem = (SharedMemPtr) malloc(sizeof(SharedMem));
	
	mem->request_list; // array containing device number and process number that owns it.
	mem->interrupt_list; // array containg the device number and process number that it is returned to
	/*mem->int_head; // head of interrupt list
	mem->int_tail; // tail of interrupt list
	mem->req_head; // head of request list
	mem->req_tail; // tail of request list	*/
	mem->timer_flag; // if 1 timer interrupt, 0 otherwise.
	mem->int_count = 0; // interrupt list count
	mem->req_count = 0; // request list count
	mem->run_flag = 1;
	
	// member functions
	mem->setTimer = timerSet;      // sets the timer
	mem->addRequest = sendRequest; // add a request to the queue
	mem->addInterrupt = sendInterrupt; // add a interrupt to the queue
	mem->getRequest = nextRequest; // get a request for the queue
	mem->getInterrupt = nextInterrupt; // get an interrupt from the queue
	mem->destruct = SharedMemDestruct; // destruct the shared memory area
	mem->toStringInt = SharedMemPrintInt; // interrupt queue tostring 
	mem->toStringReq = SharedMemPrintReq; // request queue tostring 
	
	return mem;
}

// destruct the shared memory area
int SharedMemDestruct(SharedMemPtr this)
{
	// if this is null return error
	if (this == (SharedMemPtr) NULL) { return NULL_PTR; }
	// free any nodes left in request Queue
	if(this->req_head != (DataPackPtr) NULL) 
	{
		this->req_head->destructAll(this->req_head);
	}
	// free any nodes left in interrupt queue
	if(this->int_head != (DataPackPtr) NULL) 
	{
		this->int_head->destructAll(this->int_head);
	}
	// free sharedmem area.
	free(this);
	return NO_ERROR;
}

// sets timer flag in the shared mem area
int timerSet(SharedMemPtr this, int flag, pthread_mutex_t* lock)
{
	// if this is null return error
	if (this == (SharedMemPtr) NULL) { return NULL_PTR; }
	pthread_mutex_lock(lock); // lock out other threads
	this->timer_flag = flag;	// set flag
	pthread_mutex_unlock(lock); // unlock;
	return NO_ERROR;
}

// add a request to the queue
int sendRequest(SharedMemPtr this, int pid, int did, pthread_mutex_t* lock)
{
	// create data node for link list
	DataPackPtr pack = DataPackConstruct(pid, did, NULL);
	
	// check for empty list
	if(this->req_head == NULL) 
	{	// add to empty list
		// lock out other threads from accessing iterators
		pthread_mutex_lock(lock);
		this->req_head = pack;
		this->req_tail = pack;
		// unlock critical region
		pthread_mutex_unlock(lock);
	} else 
	{	// add to list
		// lock out other threads from accessing iterators
		pthread_mutex_lock(lock);
		this->req_tail->next = pack;
		this->req_tail = pack;
		// unlock critical region
		pthread_mutex_unlock(lock);
	}
	this->req_count++;
	
	return NO_ERROR;
}

// add a interrupt to the queue
int sendInterrupt(SharedMemPtr this, int pid, int did, pthread_mutex_t* lock)
{
	// create node for linklist
	DataPackPtr pack = DataPackConstruct(pid, did, NULL);

	// check for empty list
	if(this->int_head == NULL) 
	{	// add to empty list
		// lock other threads out
		pthread_mutex_lock(lock);
		this->int_head = pack;
		this->int_tail = pack;
		// unlock critical region
		pthread_mutex_unlock(lock);
	} else
	{	// add to list
		// lock other threads out
		pthread_mutex_lock(lock);
		this->int_tail->next = pack;
		this->int_tail = pack;
		// unlock critical region
		pthread_mutex_unlock(lock);
	}
	this->int_count = this->int_count + 1;
	
	return NO_ERROR;
} 

// get a request for the queue   
// Note: request_data is a pointer to a int array. This function fills
// the array with the necessary requested data.
// This is how the inturrupt Controller gets the request data. 
// returns error code EMPTY or NO_ERROR
int nextRequest(SharedMemPtr this, pthread_mutex_t* lock, int* request_data)
{	
	// check for empty list
	if(this->req_head == NULL) 
	{	// empty Queue
		return EMPTY;
	} 
	this->req_count--;
	// lock out other threads from accessing iterators
	pthread_mutex_lock(lock);
	
	// remove from request linked list
	request_data[0] = this->req_head->pid;
	request_data[1] = this->req_head->did;
	
	DataPackPtr temp = this->req_head;		// save head pointer
	this->req_head = this->req_head->next;	// move head pointer
	temp->destruct(temp);					// free head pointer memory
	
	// unlock critical region
	pthread_mutex_unlock(lock);
	
	return NO_ERROR;
}

// get an interrupt from the queue
// Note: interrupt_data is a pointer to a int array. This function fills
// the array with the necessary interrupt data.
// This is how the CPU gets the interrupt data.  
int nextInterrupt(SharedMemPtr this, pthread_mutex_t* lock, int* interrupt_data)
{
	// check for empty list
	if(this->int_head == (SharedMemPtr) NULL) 
	{	// empty Queue
		return EMPTY;
	} 
	
	if(this->timer_flag == TIMER_INTERRUPT)
	{
		pthread_mutex_lock(lock);
		this->timer_flag = NO_INTERRUPT;
		// update interrupt data 
		interrupt_data[0] = NO_PROCESS;
		interrupt_data[1] = TIMER_INTERRUPT;
		
		// unlock critical region
		pthread_mutex_unlock(lock);
		
		return TIMER_INTERRUPT;
		
	}
	this->int_count--;
	// remove from request linked list
	
	// lock out other threads from accessing iterators
	pthread_mutex_lock(lock);
	// update interrupt data 
	DataPackPtr temp;
	DataPackPtr temp_trail = this->int_head;
	this->int_tail = this->int_head->next;
	
	// update data to first node
	interrupt_data[0] = this->int_head->pid;
	interrupt_data[1] = this->int_head->did;
	int flag = 0;
	// search for the first node with lowest priority
	while(this->int_tail != (DataPackPtr) NULL)
	{	
		// check for lower priority than current node.
		if(this->int_tail->did < interrupt_data[1])
		{
			flag = 1;
			interrupt_data[0] = this->int_tail->pid;
			interrupt_data[1] = this->int_tail->did;
			temp = temp_trail; // points to the node behind int_tail the current node.
		}
		// move iterators
		temp_trail = temp_trail->next;
		this->int_tail = this->int_tail->next;
	}
	// if temp  is null head is the target
	if (flag == 0)
	{	// update head pointer
		temp_trail = this->int_head;
		this->int_head = this->int_head->next;
		
	} else
	{	// head is not the target
		// remove target node
		temp_trail = temp->next; // save the node containing the lowest priority
		if (temp->next != (DataPackPtr) NULL)
		{
			temp->next = temp->next->next; // move the change pointers to remove the target node
		}
	}
	temp_trail->destruct(temp_trail); // free memory
	
	// unlock critical region
	pthread_mutex_unlock(lock);
	
	return NO_ERROR;
}									     

// returns a char* representation of the Interrupt Queue
char* SharedMemPrintReq(SharedMemPtr this, int* error)
{
	// check for null pointer
	if (this == NULL) 
	{
		*error = NULL_PTR;
		return 0;
	}
	char* string = (char*) malloc(sizeof(char) * (PACKSTRING * (this->req_count) + 15));
	
	snprintf(string, 15, "Request Queue\n");
	// add requests to string
	DataPackPtr current = this->req_head;
	if (this->req_count > 0)
	{   // construct string rep of teh request queue
		while(current != (DataPackPtr) NULL)
		{	// add current string
			strcat(string, current->toString(current, error));
			strcat(string, "\n");
			// next node
			current = current->next;
		}
	}// return
	return string; 
}
	
// returns a char* representation of the Request_IO Queue
char* SharedMemPrintInt(SharedMemPtr this, int* error)
{
	// check for null pointer
	if (this == NULL) 
	{
		*error = NULL_PTR;
		return 0;
	}
	// create string large enough for the interrupt queue
	char* string = (char*) malloc(sizeof(char) * (PACKSTRING * (this->int_count) + 17));
	// create char* representation of the dataPack
	snprintf(string, 17, "Interrupt Queue\n");
	
	// add interrupts to string
	DataPackPtr current = this->int_head;
	if (this->int_count > 0)
	{	// construct tostring of the interrupt queue
		while(current != (DataPackPtr) NULL)
		{
			// add current string rep.
			strcat(string, current->toString(current, error));
			strcat(string, "\n");
			// move to next node.
			current = current->next;
		}
	}
	// return
	return string; 
}

void* testAdd(void* args)
{
	pthread_mutex_t lock;
	pthread_mutex_init(&lock, NULL);
	SharedMemPtr mem = (SharedMemPtr) args;
	unsigned int usecs = 10;
	
	usleep(usecs);	
	int i;
	for (i = 0; i < 10; i++)
	{
		printf("Add Request %d, returned code: %d\n", i, mem->addRequest(mem, i, i, &lock));
		sched_yield();
		sched_yield();
		sched_yield();
		sched_yield();
		sched_yield();
	}
	
	for (i = 0; i < 10; i++)
	{
		printf("Add Interrupt %d, returned code: %d\n", i, mem->addInterrupt(mem, i, i, &lock));
		sched_yield();
		sched_yield();
		sched_yield();
		sched_yield();
		sched_yield();
		sched_yield();
	}

}

void* testGet(void* args)
{

	pthread_mutex_t lock;
	pthread_mutex_init(&lock, NULL);
	SharedMemPtr mem = (SharedMemPtr) args;
	
	unsigned int usecs = 10;
	
	usleep(usecs);	
	int data[2];
	int i;
	for (i = 0; i < 10; i++)
	{
		printf("Get Request %d, returned code: %d\n", i, mem->getRequest(mem, &lock, &data[0]));
	}
	
	for (i = 0; i < 10; i++)
	{
		printf("Get Interrupt %d, returned code: %d\n", i, mem->getInterrupt(mem, &lock, &data[0]));
	}

}
/*
// test for shared memory area.
int main(void)
{
	SharedMemPtr mem = SharedMemConstruct();
	int error = 0;
	pthread_t adder;
	pthread_t getter;
	printf("Creating threads\n");

	error = pthread_create(&adder, NULL, testAdd, mem); // runs test function testAdd
	if (error != 0) {printf("Error Creating thread: %d", error); }
		
	error = pthread_create(&getter, NULL, testGet, mem); // runs test function testGet
	if (error != 0) {printf("Error Creating thread: %d", error); }
	
	pthread_join(getter, NULL);
	pthread_join(adder, NULL);
	
	printf("\n");
	printf(mem->toStringInt(mem, &error));
	printf("\n");
	printf(mem->toStringReq(mem, &error));
}*/
